package com.net.xpay.core.mybatis.collection;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.net.common.enums.base.IntEnum;
import com.net.common.enums.base.StringEnum;
import com.net.common.util.JacksonMapperUtil;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class JsonListHandler implements TypeHandler<Object> {
    private static final String LONG_CLASS_NAME = "java.lang.Long";
    private static final String INTEGER_CLASS_NAME = "java.lang.Integer";
    private static final String STRING_CLASS_NAME = "java.lang.String";

    private static final char SPLIT = '/';  //json数据和类名的分隔符号

    private static final String EMPTY_COLLECTION = "empty_collection";

    private static Map<String, Class<?>> classMap = new ConcurrentHashMap<>();

    private static ObjectMapper mapper;

    static {
        mapper = JacksonMapperUtil.create();
    }

    @Override
    public void setParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
        if (parameter == null) {
            ps.setString(i, null);
            return;
        }
        try {
            if (!(parameter instanceof List)) {
                throw new SQLException("the collection type for JsonListHandler is not a list");
            }
            List actualParam = (List) parameter;
            String json = mapper.writeValueAsString(parameter);
            json = json + SPLIT + parameter.getClass().getName();
            if (actualParam.isEmpty()) {
                json = json + SPLIT + EMPTY_COLLECTION;
            } else {
                json = json + SPLIT + actualParam.get(0).getClass().getName();
            }
            ps.setString(i, json);
        } catch (Exception e) {
            throw new SQLException(e);
        }
    }

    @Override
    public Object getResult(ResultSet rs, String columnName)
            throws SQLException {
        String json = rs.getString(columnName);
        return jsonToObject(json);
    }

    @Override
    public Object getResult(ResultSet rs, int columnIndex) throws SQLException {
        String json = rs.getString(columnIndex);
        return jsonToObject(json);
    }

    @Override
    public Object getResult(CallableStatement cs, int columnIndex)
            throws SQLException {
        String json = cs.getString(columnIndex);
        return jsonToObject(json);
    }

    /**
     * json 转换成对象
     */
    private Object jsonToObject(String json) throws SQLException {
        if (json == null) {
            return null;
        }
        int index = json.lastIndexOf(SPLIT);
        if (index < 0) {
            throw new SQLException("the item name for json collection not find");
        }
        String itemName = json.substring(index + 1, json.length());
        json = json.substring(0, index);

        index = json.lastIndexOf(SPLIT);
        if (index < 0) {
            throw new SQLException("the class name for json collection not find");
        }
        String className = json.substring(index + 1, json.length());
        json = json.substring(0, index);

        Class<?> clazz;
        try {
            clazz = classMap.get(className);
            if (clazz == null) {
                clazz = Class.forName(className);
                classMap.putIfAbsent(className, clazz);
            }
        } catch (ClassNotFoundException e) {
            throw new SQLException("the class for json collection not find|className=" + className, e);
        }

        if (!List.class.isAssignableFrom(clazz)) {
            throw new SQLException("json to object should be list object ");
        }

        if (EMPTY_COLLECTION.equals(itemName)) {
            return new ArrayList<>();
        }

        try {
            if (itemName.equals(LONG_CLASS_NAME)) {
                return mapper.readValue(json, new TypeReference<List<Long>>() {});
            }
            if (itemName.equals(INTEGER_CLASS_NAME)) {
                return mapper.readValue(json, new TypeReference<List<Integer>>() {});
            }
            if (itemName.equals(STRING_CLASS_NAME)) {
                return mapper.readValue(json, new TypeReference<List<String>>() {});
            }

            return processEnumItem(json, itemName);
        } catch (SQLException e1) {
            throw e1;
        } catch (Exception e) {
            throw new SQLException(e);
        }
    }

    private Object processEnumItem(String json, String itemClassName) throws Exception {
        Class<?> clazz;
        try {
            clazz = classMap.get(itemClassName);
            if (clazz == null) {
                clazz = Class.forName(itemClassName);
                classMap.putIfAbsent(itemClassName, clazz);
            }
        } catch (ClassNotFoundException e) {
            throw new SQLException("the class for json collection item not find", e);
        }
        if (IntEnum.class.isAssignableFrom(clazz)) {
            List<Integer> intList = mapper.readValue(json, new TypeReference<List<Integer>>() {});
            List<IntEnum> res = new ArrayList<>();
            Map<Integer, IntEnum> map = new HashMap<>();
            for (Object e : clazz.getEnumConstants()) {
                map.putIfAbsent(((IntEnum) e).getValue(), (IntEnum) e);
            }

            for (Integer i : intList) {
                if (i == null) {
                    continue;
                }
                res.add(map.get(i));
            }
            return res;
        } else if (StringEnum.class.isAssignableFrom(clazz)) {
            List<String> strList = mapper.readValue(json, new TypeReference<List<String>>() {});
            List<StringEnum> res = new ArrayList<>();
            Map<String, StringEnum> map = new HashMap<>();
            for (Object e : clazz.getEnumConstants()) {
                map.putIfAbsent(((StringEnum) e).getValue(), (StringEnum) e);
            }

            for (String str : strList) {
                if (str == null) {
                    continue;
                }
                res.add(map.get(str));
            }
            return res;
        } else {
            throw new SQLException("the class not right for json collection|itemClassName=" + itemClassName);
        }
    }
}
